/**
 *  Copyright 2007-2008 University Of Southern California
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package edu.isi.pegasus.planner.code.generator.condor;

/**
 * A utility class to correctly quote arguments strings before handing over
 * to Condor.
 * <p>
 * The following Condor Quoting Rules are followed while quoting a String.
 *
 * <pre>
 * 1) \' => ''   e.g \'Test\' is converted to ''Test''
 * 2) \" => ""   e.g \"Test\" is converted to ""Test""
 * 3) '  => '    if not enclosed in surrounding double quotes
 *               e.g 'Test' is converted to 'Test'
 * 4) '  => ''   if enclosed in surrounding double quotes
 *               e.g "'Test'" is converted to ''Test''
 * 5) "  => '    if not enclosed in surrounding single quotes
 *               e.g Karan "Vahi" is converted to Karan 'Vahi'
 * 6) "  => ""   if enclosed in surrounding single quotes.
 *               e.g 'Karan "Vahi"' is converted to 'Karan ""Vahi""'.
 * 7) *  =>  *   if enclosed in single or double quotes, the enclosed characters
 *               are copied literally including \ (no escaping rules apply)
 * 8) \\ => \    escaping rules apply if not enclosed in single or double quotes.
 *               e.g \\\\ becomes \\, and \\\ throws error.
 * </pre>
 *
 * In order to pass \n etc in the arguments, either quote it or escape it.
 * for e.g in the DAX the following are valid ways to pass Karan\nVahi to the
 * as arguments
 * <pre>
 *   1) "Karan\nVahi"
 *   2) 'Karan\nVahi'
 *   3) Karan\\nVahi
 * </pre>
 *
 * In addition while writing out to the SubmitFile the whole argument String
 * should be in enclosing ". for e.g arguments = "Test";
 *
 * @author Karan Vahi
 * @author Gaurang Mehta
 *
 * @version $Revision: 2090 $
 */
public class CondorQuoteParser {

    /**
     * Table to contain the state transition diagram for the parser. The
     * rows are defined as current states 0 through 7. The columns is the
     * current input character. The cell contains first the action to be
     * taken, followed by the new state to transition to:
     *
     * <pre>
     *      | EOS |  \  |  '  |  "  |other|
     *      |  0  |  1  |  2  |  3  |  4  |
     * -----+-----+-----+-----+-----+-----+
     *   0  | -,F |  -,1| A2,2| A2,3| A1,0|
     *   1  | -,E1| A1,0| A3,0| A4,0| A1,0|
     *   2  | -,E2| A1,2| A2,0| A4,2| A1,2|
     *   3  | -,E3| A1,3| A3,3| A2,0| A1,3|
     * -----+-----+-----+-----+-----+-----+
     *   F  |  4  | final state
     *   E1 |  5  | error1: unexpected end of input
     *   E2 |  6  | error2: unmatched single quotes
     *   E3 |  7  | error3: unmatched double quotes
     * </pre>
     *
     * The state variable collects the new state for a given
     * state (rows) and input character set (column) identifier.
     *
     * <p>
     * The state diagram for the above table is shown as follows
     *
     * <br>
     * <a href="doc-files/CondorQuote.jpg">
     * <img src="doc-files/CondorQuote.jpg" height="350" width="400"></a>
     *
     *
     */
    private static final byte cState[][] = {
        // E   \   '   "   O
        {  4,  1,  2,  3,  0}, // 0: starting state
        {  5,  0,  0,  0,  0}, // 1: found a \
        {  6,  2,  0,  2,  2}, // 2: found an opening single quote
        {  7,  3,  3,  0,  3}, // 3: found an opening double quote
    };


    /**
     * There are five identified actions.
     *
     * <pre>
     *  -   | 0 | noop
     *  A1  | 1 | append input character to result
     *  A2  | 2 | append '  to result
     *  A3  | 3 | append '' to result
     *  A4  | 4 | append "" to result
     * </pre>
     *
     * The action variable collects the action to take for a
     * given state (rows) and input character set (column).
     */
    private static final byte cAction[][] = {
        // E   \   '   "   O
        {  0,  0,  2,  2,  1}, // 0: starting state
        {  0,  1,  3,  4,  1}, // 1: found a \
        {  0,  1,  2,  4,  1}, // 2: found an opening single quote
        {  0,  1,  3,  2,  1}, // 3: found an opening double quote
    };


    /**
     * Parses a string and condor quotes it. The enclosing quotes are not
     * generated around the String.
     *
     * @param s       is the input string to parse and quote.
     *
     * @return the quoted String.
     * @throws CondorQuoteParserException if the input cannot be recognized.
     */
    public static String quote( String s) throws CondorQuoteParserException{
        return quote( s , false );
    }


    /**
     * Parses a string and condor quotes it. Enclosing quotes are generated
     * around the whole string if boolean enclose parameter is set.
     *
     * @param s       is the input string to parse and quote.
     * @param enclose boolean indicating whether to generate enclosing quotes or
     *                not.
     *
     * @return the quoted String.
     * @throws CondorQuoteParserException if the input cannot be recognized.
     */
    public static String quote( String s, boolean enclose ) throws CondorQuoteParserException{
        StringBuffer result = new StringBuffer();

        //enclose the string with mandatory " to start
        if(enclose) result.append("\"");

        int index = 0;
        byte charset, state = 0;
        char ch = '?';

        while ( state < 4 ) {
            //
            // determine character class
            //
            switch ( (ch = ( index < s.length() ? s.charAt(index++) : '\0' )) ) {
                case '\0':
                    charset = 0;
                    break;

                case '\\':
                    charset = 1;
                    break;

                case '\'':
                    charset = 2;
                    break;

                case '\"':
                    charset = 3;
                    break;

                default:
                    charset = 4;
                    break;
            }

            //
            // perform action
            //
            switch ( cAction[state][charset] ) {
                case 0 :// do nothing
                    break;

                case 1: // append the character to the result
                    result.append(ch);
                    break;

                case 2: // append \ to the result
                    result.append('\'');
                    break;

                case 3: // append '' to the result
                    result.append("\'\'");
                    break;

                case 4: // append "" to the result
                    result.append("\"\"");
                    break;
            }

            //
            // progress state
            //
            state = cState[state][charset];
        }

        if ( state > 4 ) {
            switch ( state ) {
                case 5:
                    //we have unmatched single quotes
                    throw new CondorQuoteParserException("Unexpected end of input in string " + s,
                                                          index);


                case 6:
                    //we have unmatched single quotes
                    throw new CondorQuoteParserException("Unmatched Single Quotes in string " + s,
                                                        index);

                case 7:
                    //we have unmatched double quotes
                    throw new CondorQuoteParserException("Unmatched Double Quotes in string " + s,
                                                         index );

                default:
                    throw new CondorQuoteParserException( "Unknown error", index );
            }
        }

        //end the result with the mandatory closing " to end
        if( enclose ) result.append("\"");

        return result.toString();
    }

    /**
     * A Test program.
     */
    public static void main(String[] args) {
        test("Test Input");            //result should be Test Input
        test("'Test Input'");          //result should be 'Test Input'
        test("\"Test Input\"");        //result should be 'Test Input'
        test("\\'Test Input\\'");      //result should be ''Test Input''
        test("\\\"Test Input\\\"");    //result should be ""Test Input""
        test("\\\'Test Input\\\'");    //result should be ''Test Input''
        test("\"\'Test Input\'\"");    //result should be '''Test Input'''
        test("\'\"Test Input\"\'");    //result should be '""Test Input""'
        test("\"\'Test \\Input\'\"");  //result should be '''Test \Input'''
        test("\\\"Test Input\\\"");    //result should be ""Test Input""
        test("\'Test \"Input\"\'");    //result should be 'Test ""Input""'
        test("Test \"Input\"");        //result should be Test 'Input'
        test("\\\\Test Input");        //result should be \Test Input
        test("\\\\");                  //result should be \
        test("\'\"Test Input\'");      //result should be '""Test Input'


        //errorneous inputs
        test("\'\"Test Input\"  ");
        test(" \"\"\" ");
        test(" '''  ");
    }


    /**
     * Helper test method that tries and catches exception
     *
     * @param s the string to be parsed.
     */
    private static void test(String s){
        try{
            System.out.println(s + " condor quoted is " + quote(s) );
        }
        catch(CondorQuoteParserException e){
            System.out.println("Error " + e + " at position " + e.getPosition());
        }
        //System.out.println();
    }



}
